﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace TeamTasker.Utils
{
    public class TreeHelper
    {
        public static void Traverse<T>(IEnumerable<T> source,
                                       Func<T, IEnumerable<T>> childrenGetter,
                                       Action<T> markNodeAction)
        {
            foreach (T node in source)
            {
                IEnumerable<T> children = childrenGetter(node);
                if (children != null)
                {
                    Traverse(children, childrenGetter, markNodeAction);
                }
                markNodeAction(node);
            }
        }

        public static void FillInTree<T>(IEnumerable source,
                                         IList<T> destination,
                                         Func<object, IEnumerable> sourceChildrenGetter,
                                         Func<T, IList<T>> destinationChildrenGetter,
                                         Action<T, T> destinationParentSetter,
                                         Func<object, int, T> createNewNodeFunction) where T : class
        {
            FillInTree(source, -1, destination, sourceChildrenGetter, destinationChildrenGetter, null,
                       destinationParentSetter,
                       createNewNodeFunction);
        }

        private static void FillInTree<T>(IEnumerable source,
                                          int currentNodeLevel,
                                          IList<T> destination,
                                          Func<object, IEnumerable> sourceChildrenGetter,
                                          Func<T, IList<T>> destinationChildrenGetter,
                                          T parentNode,
                                          Action<T, T> destinationParentSetter,
                                          Func<object, int, T> createNewNodeAction)
        {
            destination.Clear();
            currentNodeLevel++;
            foreach (object node in source)
            {
                T newDestinationNode = createNewNodeAction(node, currentNodeLevel);
                destination.Add(newDestinationNode);
                IEnumerable sourceChildren = sourceChildrenGetter(node);
                if (sourceChildren != null)
                {
                    FillInTree(sourceChildren, currentNodeLevel, destinationChildrenGetter(newDestinationNode), sourceChildrenGetter,
                               destinationChildrenGetter, newDestinationNode, destinationParentSetter,
                               createNewNodeAction);
                }
                destinationParentSetter(newDestinationNode, parentNode);
            }
        }

        public static void FillInTree<TSourceNode, TDestinationNode>(IEnumerable<TSourceNode> source,
                                                                     IList<TDestinationNode> destination,
                                                                     Func<TSourceNode, IEnumerable<TSourceNode>>
                                                                         sourceChildrenGetter,
                                                                     Func<TDestinationNode, IList<TDestinationNode>>
                                                                         destinationChildrenGetter,
                                                                     Action<TDestinationNode, TDestinationNode>
                                                                         destinationParentSetter,
                                                                     Func<TSourceNode, int, TDestinationNode>
                                                                         createNewNodeFunction)
            where TDestinationNode : class
        {
            FillInTree(source, -1, destination, sourceChildrenGetter, destinationChildrenGetter, null,
                       destinationParentSetter,
                       createNewNodeFunction);
        }

        private static void FillInTree<TSourceNode, TDestinationNode>(IEnumerable<TSourceNode> source,
                                                                      int currentNodeLevel,
                                                                      IList<TDestinationNode> destination,
                                                                      Func<TSourceNode, IEnumerable<TSourceNode>>
                                                                          sourceChildrenGetter,
                                                                      Func<TDestinationNode, IList<TDestinationNode>>
                                                                          destinationChildrenGetter,
                                                                      TDestinationNode parentNode,
                                                                      Action<TDestinationNode, TDestinationNode>
                                                                          destinationParentSetter,
                                                                      Func<TSourceNode, int, TDestinationNode>
                                                                          createNewNodeAction)
        {
            destination.Clear();
            currentNodeLevel++;
            foreach (TSourceNode node in source)
            {
                TDestinationNode newDestinationNode = createNewNodeAction(node, currentNodeLevel);
                destination.Add(newDestinationNode);
                IEnumerable<TSourceNode> sourceChildren = sourceChildrenGetter(node);
                if (sourceChildren != null)
                {
                    FillInTree(sourceChildren, currentNodeLevel, destinationChildrenGetter(newDestinationNode), sourceChildrenGetter,
                               destinationChildrenGetter, newDestinationNode, destinationParentSetter,
                               createNewNodeAction);
                }
                destinationParentSetter(newDestinationNode, parentNode);
            }
        }

        public static void SearchAllOccurences<T>(IEnumerable<T> source,
                                               Func<T, IList<T>> childrenGetter,
                                               Predicate<T> conditionToCheck,
                                               Action<T> actionOnSuccess,
                                               Action<T> actionOnFail)
        {
            foreach (T node in source)
            {
                if (conditionToCheck(node))
                {
                    actionOnSuccess(node);
                }
                else
                {
                    actionOnFail(node);
                }
                IEnumerable<T> children = childrenGetter(node);
                if (children != null)
                {
                    SearchAllOccurences(children, childrenGetter, conditionToCheck, actionOnSuccess, actionOnFail);
                }
            }
        }

        public static T SearchFirstOccurence<T>(IEnumerable<T> source,
                                               Func<T, IList<T>> childrenGetter,
                                               Predicate<T> conditionToCheck)
        {
            foreach (T node in source)
            {
                if (conditionToCheck(node))
                {
                    return node;
                }
                IEnumerable<T> children = childrenGetter(node);
                if (children != null)
                {
                    SearchFirstOccurence(children, childrenGetter, conditionToCheck);
                }
            }
            return default(T);
        }
    }
}